To cope with the rising complexity of computer programs, many modern programming languages use discrete programming structures called "objects" that present well-defined interfaces to the outside programming environment and are, in principle, self-contained. Objects typically contain information and methods that operate on the contained information or external information.
The pieces of information associated with an object are referred to as "properties" of the object. Each property has a name and can be assigned a value. The type of datum that can be assigned to a property as its value may be, for example, a datum of a simple type, such as an integer or a floating point number, or a datum of a complex type, such as a data structure or another object. When an object is defined, a programmer typically declares the properties that the object contains and specifies the data type for each property. The data type of a property indicates the type of data that the property can represent. For example, a property of the type "integer" can have values 1, 2, 3, etc., whereas a property of the type "pointer to a method" contains a memory address of the beginning of a set of instructions. The properties of an object collectively define its structure.
Each property of an object has associated attributes, such as its name, type, accessibility (e.g., in C++: public, protected, or private), and position within the object. The values of these attributes are not typically accessible to the programmer, i.e., a programmer cannot typically query an object to determine its type or its availability. In C++, a programmer can query the size of a property and could indirectly determine its position within an object by subtracting a pointer to the property from a pointer to the object. The inability to determine attributes of an object further limits programmers from specifying how a program will function.
A method comprises one or more lines of computer code that perform an operation. A method is typically executed by departing from the sequential execution of program lines at the point of the method call, jumping to the lines of code constituting the method, and then returning to the sequential execution of the program below the method call. In object-oriented programming, methods are typically defined as part of a class definition and can be applied only to objects of the defining class, or objects of classes derived from the defining class. The methods that can be applied to an object are collectively called the "behavior" of the object.
One fundamental goal of object-oriented programming is to encapsulate properties and methods within an object so that a second programmer can use an object defined by a first programmer after learning only the interface of the object. The interface contains code needed to send data to an object and to receive output, such as calculation results, error codes, and object status, from the object. The second programmer should not need to understand the internal workings of the object. Programmers should, therefore, be able to write programs that access objects defined by other programmers and reuse previously defined objects as building blocks of new programs. For example, once a programmer defines an object that represents an automated teller machine, another programmer should be able to use the automated teller machine object in different programs that, for example, model the same automated teller machines in a different banking network. Encapsulation of objects also allows a programmer to modify the internal workings of a particular object to eliminate an error or improve efficiency without having to change other parts of the program that use the object. Encapsulation thus reduces complexity in large programming endeavors.
Another fundamental goal of object-oriented programming is extensibility, i.e., allowing previously created objects to be extended to include additional properties or functionality. This is accomplished by allowing one object to inherit properties and methods from another source, such as a parent object. For example, if a new type of automated teller machine having additional features becomes available, a programmer should be able to easily create, based upon an existing automated teller machine object, a new automated teller machine object that has additional properties and materials representing the additional features. By eliminating the duplication of programming efforts, inheritance allows existing object-oriented code to be expanded into new uses. Inheritance also facilitates building complex objects from relatively simple building blocks.
The class-instance programming model is one technique for implementing software objects. In the class-instance programming model, abstract objects, known as class objects, are defined, and then instances of the class can be declared. A class object defines the structure and methods of instances of the class, but properties in the abstract class itself do not have values. An instance of a class is an object having the structure defined by the class and capable of being assigned property values. All instances of a class, therefore, have a separate memory structure for storing the property values of that instance. Unlike properties, there is typically only one copy of each of the methods of a class. Method calls, therefore, typically include as an "invisible parameter" an indication of which instance of the class is calling or being passed to the class method.
To facilitate the creation of new classes in the class-instance programming model, a "child" class can be defined that is derived from and inherits the structure of its parent class. An instance of the child class would, therefore, have all the properties and methods defined in the parent class and any additional properties and methods defined specifically in the child class. For example, a class "Vehicle" may have derived from it a child class "Spacecraft," an instance of which is a particular spacecraft, e.g., the space shuttle Discovery. Because the child class (Spacecraft) is a specific type of the more general parent class (Vehicle), this type of inheritance is known as an "is-a" inheritance.
To take advantage of inheritance, object-oriented programming languages are often sold with libraries of predefined abstract classes that aid programmers in building new classes and that promote use of standardized objects to build application software. Class libraries theoretically greatly simplify creation of new software.
Many programming languages allow multiple inheritance, i.e., inheriting properties and methods from more than one parent object. For example, FIGS. 1A, 1B, and 1C show, respectively, an "Airplane" class 10, a "Boat" class 12, and a "Seaplane" class 14 that inherits from the Boat and Airplane classes. FIG. 1C shows that the memory structure 16 of an instance of the derived Seaplane class 14 is a concatenation of the memory structures 18 and 20 of Boat class 12 and Airplane class 10, respectively, the concatenation being performed in the order that the inheritance was declared. (The heavy lines of FIGS. 1A, 1B, and 1C enclose the actual memory structures and separate them from label information included in the drawings to facilitate understanding of the prior art and the invention. This convention is also followed in other figures in this specification.) Thus, properties having the same name in multiple parent classes are typically inherited in the children as separate properties. With duplicate method names, however, one method will override another and only one method will be inherited.
Unfortunately, multiple inheritance can cause encapsulation to break down in complex programming environments. One difficulty arises because inheritance lacks granularity, i.e., it is an all-or-nothing proposition. A derived class inherits the entire set of properties and methods of its parent class or classes; a child class cannot inherit only specified properties or methods from individual parents. The following example illustrates the point.
FIG. 2A shows an airplane 24, having an engine 22, a fuselage 26, landing gear 28, wings 30, a rudder 32 and a propeller 34. FIG. 2B shows a boat 36 having a hull 38, a mast 40, a sail 42, an anchor 44, a rudder 46 and riggings 48. In accordance with prior art multiple inheritance, Seaplane class 14 inherits all the properties and methods of Airplane class 10 and Boat class 12, even if some of the properties are redundant or inappropriate for a Seaplane.
FIGS. 2C and 2D illustrate the absurdity of all-or-nothing inheritance. All the components of Boat 36 and Airplane 24 are force-fit into a composite seaplane. FIG. 2C shows a Seaplane 50 that inherited first from Boat 36 and then from Airplane 24. FIG. 2D shows a Seaplane 52 that inherited first from Airplane 24 and then from Boat 36. In both cases, the Seaplane object has some inappropriate properties, such as a Sail 42, and duplicate properties, such as Rudders 32 and 46, Rudder 32 being an aircraft rudder and Rudder 46 being a boat rudder. When a programmer refers to the Rudder property of the Seaplane object, it is unclear which rudder will be referenced.
To avoid some of the absurd results from multiple inheritance and produce a useful derived class, it is sometimes necessary to modify one or more of the parent classes. For example, it may be necessary to change Boat class 12 by eliminating Sail 42 to produce a derived Seaplane class that accurately models a Seaplane. Changing Boat class 12, however, can have consequences in other parts of a program that use objects that inherit properties and methods from the original Boat class 12. For example, a programmer would be required to track down other objects and classes that inherit from the modified Boat class to ensure that other sections of the program are not unintentionally affected. Needing to understand and modify the internal definitions of parent classes is anathema to the concept of encapsulation, which dictates that objects be self-contained so that users of an object need not understand the internal workings of the object. Moreover, changes to a class object in a class library will often require not only recompilation of the library itself, but also recompilation of existing software that uses the changed class object. The problems demonstrated by the foregoing example arise in part because a programmer cannot specify which aspects of a derived class will be inherited from one parent class and which aspects will be inherited from another parent class. A small modification in a parent class may be inappropriate if inherited in all instances of child classes and a modification cannot be made to a single instance if class inheritance is used to provide the change.
If a particular property name or method name appears in more than one parent object, the method that will be inherited and the order of properties in the child class depends upon the order of inheritance and upon arbitration rules. For example, a programmer may wish to define a class E (FIG. 3) that inherits properties and methods from classes C and D, which are defined in two different class libraries, possibly from different software manufacturers. If some of the properties or methods in class A have the same names as properties or methods in class B, the methods and order of properties in classes C and D will depend on the order of inheritance from classes A and B. To function properly, class C may require that a method be inherited from class A, rather than from class B, or that the properties in class C be in a particular order. In the multiple inheritance example of FIG. 3, in which class E inherits from classes C and D, each of which inherits in a different order from classes A and B, it is very difficult to determine the resulting methods and property order in class E. In some cases, class E may be defective because a method inherited from class C may depend upon a property being inherited first from class B, whereas a method inherited from class D may depend upon the same property being inherited first from class A.
Applying arbitration rules to objects having multiple parent classes, each of which can also have multiple parents, can quickly become very complex. The arbitration rules require a detailed knowledge of the structure of the parent classes in the inheritance tree. Because complex classes in class libraries are often built upon simpler classes in the library, a programmer may need to understand the structure of many of the classes in the library before he or she is able to predict how an object he or she creates using class objects from the library will function.
For this reason, programmers that produce class libraries typically provide source code for their class libraries so that applications programmers can determine the inheritance order and redefine classes in the library if necessary. To require knowledge of the parent objects and of complex arbitration rules greatly increases the complexity of object-oriented programming and related source code, vitiates the purpose of encapsulation, and reduces the reusability and extensibility of code. This defeats the purpose of having extensible objects as a result of encapsulation and inheritance and requires extensive knowledge of preexisting class libraries by the programmer.
The complexity and inflexibility of inheritance in languages such as C++ derives in part from the close relationship between the memory structure in which an object is stored and its class definition. As shown in FIG. 1C, a child object is stored in memory as a concatenation of the objects from which it derives. This allows the child object to be "type cast," i.e., treated as though it were of the same object type as the parent object. Such a system, however, requires that all properties of an object be inherited to maintain the relationship in memory of the different components of a derived object. The rigid memory structure in which properties of objects are stored in the order in which they are declared and inherited also constrains the allocation of memory by a compiler, thereby causing inefficient use of available memory. The compiler is not free to reorder or eliminate duplicate properties when such reordering or elimination of duplication would produce more efficient memory usage.
The object programming model is another technique for implementing software objects. In the object programming model, objects are defined and can then be copied or used to derive other objects. This technique differs from the class-instance model in that there are no abstract classes. In some respects, the object model is more flexible because objects are independent entities and can be created and modified independently of any definition of abstract classes. Moreover, methods can be attached to individual objects, rather than to all members of a class. The object model is also consistent with the use of visual programming tools, such as Microsoft's Visual Basic.TM., to create graphical user interfaces. In using such tools, programmers can create, for example, menu objects on the screen, without having to first create an abstract menu class.
In the object model, objects can be created by copying them from other objects, but there are no links between the original object and the newly created object which receives the defined structure and assigned property values from the original object. Property values in the new object do not change when property of the original is changed. Visual Basic.TM., Asymetrix's Toolbook.TM., and Apple's Hypercard.TM. are examples of applications based upon an object model without is-a inheritance.
A variation on the object programming model, the object-prototype programming model, provides for inheritance. When the value of a property of a parent object changes, the corresponding property in the child object also changes. A child object in the object-prototype model inherits from a particular parent object, rather than from an abstract class as in class-instance model inheritance, thereby allowing an object to inherit actual property values, in addition to structure. As in the class-instance model, inheritance in the object-prototype model is typically based upon an is-a hierarchy, i.e., a derived object is a type of the parent object. Applications using the object-prototype models are not in widespread commercial use.
Another approach to object-oriented language architecture is the use of a containment, or "has-a," hierarchy as opposed to an is-a hierarchy. In a containment hierarchy, a child object is contained in a parent object, instead of being a type of the parent object. An object is contained within only a single container, although the parent container can itself be contained within another container.
Most implementations do not allow inheritance from the parent in a container hierarchy. In some applications, such as Hypercard.TM. and Toolbook.TM., when a method cannot be found associated with an object, the container of the object is checked to determine whether the container defines the method. Such a technique can be considered inheritance of methods from a parent container.
In summary, the class-instance programming model uses abstract classes that define a structure and methods. The structure and methods of one or more abstract classes can be inherited by a child abstract class. Inheritance in the class-instance model is based upon an is-a hierarchy, in which child classes are a type of the parent class and a child class inherits everything in its parent class or classes. Instances, which include property values, are created from the abstract classes, but instances cannot be derived from other instances. Therefore, structure and methods can be inherited, but property values cannot be. This restricts reusability and makes the programmer's task more complex. Expert knowledge of complex rules of arbitration and of the parent classes are often required to create specific child classes and to understand inherited behavior.
In the object programming model, objects can be copied from other objects, so structure, behavior, and data can be derived. Object systems allow copying of objects, but do not provide for inheritance. Object-prototypes systems provide for inheritance, with the inheritance hierarchies based upon is-a relationships in which each child is a type of the parent. In some applications, limited inheritance of methods is based upon has-a relationships, in which each child is contained in the parent. Has-a inheritance relationships are generally inconsistent with inheritance of property values.
Although the mission of object-oriented programming is to create modular applications using reusable and extensible objects, this promise remains largely unfulfilled in standard object-oriented programming. Classical object-oriented programming is hampered by the complexity of native object classes adopted by programming languages (such as C++), which complexity results in well-known problems in object definition and encapsulation and the related difficulty in dynamically changing objects and methods during application run-time.
Recognizing problems with multiple inheritance in the standard object-oriented language architecture, the prior art takes several approaches to programming unique objects, including binding objects at run-time, modifying and recompiling parent class libraries, and writing custom code to override the inherited properties or methods and assert the class membership of a branch object instead of letting the program architecture determine inheritance. The properties and behavior of an object, however, become dependent on its parent classes and its custom coding, thereby causing difficulty in determining how the object will behave. Complexity of the program architecture increases. The apparent solutions treat only the symptoms and produce further difficulties of their own, making it difficult to create code that is reusable and extensible by other developers.
As a result, current object-oriented programming systems require knowledge of complex rules for arbitrating inheritance and require that a programmer learn entire native class libraries to obtain consistent behavior from objects, determine inheritance, and debug code.